package com.vis.view.ui
{
	import com.adobe.serialization.json.JSON;
	import com.vis.events.VisComplete;
	import com.vis.uitls.DateUtil;
	import com.vis.vo.Column;
	import com.vis.vo.TimeLine;
	
	import flare.display.DirtySprite;
	import flare.display.TextSprite;
	import flare.query.methods.eq;
	import flare.util.Dates;
	import flare.vis.Visualization;
	import flare.vis.controls.ClickControl;
	import flare.vis.controls.TooltipControl;
	import flare.vis.data.Data;
	import flare.vis.data.DataSprite;
	import flare.vis.data.EdgeSprite;
	import flare.vis.data.NodeSprite;
	import flare.vis.data.Tree;
	import flare.vis.events.SelectionEvent;
	import flare.vis.events.TooltipEvent;
	import flare.vis.operator.encoder.ColorEncoder;
	import flare.vis.operator.label.RadialLabeler;
	import flare.vis.operator.layout.BundledEdgeRouter;
	
	import flash.display.DisplayObject;
	import flash.geom.Rectangle;
	import flash.text.TextFormat;
	import flash.utils.Dictionary;
	
	[SWF(backgroundColor="#ffffff", frameRate="30")]
	public class Diagram extends App
	{
		[Embed(source="verdana.TTF", fontName="Verdana")] // embed the font to the visualization
		private static var _font:Class;
		
		
		private var vis:Visualization;
		private var jsonString:String;
		private var fmt:TextFormat = new TextFormat("Verdana", 9);
		
		private var _columns:Array;
		private var toolTip:TooltipControl;
		private var edgeTool:TooltipControl;
		private var data:Data;
		private var _legendElements:Dictionary;
		private var _currentLineOption:Column;
		private var isLableNull:Boolean = false;
		private var _timeLine:TimeLine;
	
		public function Diagram(jsonString:String, bounds:Rectangle = null)
		{
			super(bounds);
			
			this.jsonString = jsonString;
			
		}
		override protected function init():void{	
			
			visualize();
		}
		
		
		public function visualize(lineOption:Column = null , sortBy:String = null):void{
			
			if(this.vis && this.contains(vis)) {
				removeChild(this.vis);
			}
			
			this.data = (this.data) ? this.data: buildData();
			this.data.nodes.setProperties({
				shape: null,                  // no shape, use labels instead
				visible: eq("childDegree",0), // only show leaf nodes
				buttonMode: true				
			});
			
			this.data.edges.setProperties({
				  buttonMode: true,
				  lineColor: 0xff000000,
				  lineWidth: 1
				 
			});
			
			this.vis = new Visualization(this.data); 
			if(lineOption == null) {
				for (var i:uint = 2; i < this.columns.length; i++){
					if (columns[i].type != Column.DATE){
						lineOption = columns[i];
						this.currentLineOption = lineOption;
						break;
					}
				}	
			} else {
				this.currentLineOption = lineOption;
			}
			
			addOperators(lineOption);		
			addControls();
			addChild(this.vis);
			complete();					
			resize(_appBounds.clone());
			
		}
		public function showEdges(toDate:Date):void {
			for each (var edge:EdgeSprite in this.vis.data.edges){
			  if(DateUtil.lte(edge.data[this.timeLine.dateField], toDate)){
				  edge.visible = true;
			  } else {
				  edge.visible = false;
			  }
			}
		}
		
		private function complete():void {
			var event:VisComplete = new VisComplete();
			event.columns = this.columns;
			dispatchEvent(event);
		}
		private function createColorOperator(column:Column):ColorEncoder {
			
			var edgeColor:ColorEncoder = new ColorEncoder("data." + column.label, 
				Data.EDGES, "lineColor");
			return edgeColor;
		}
		private function addOperators(lineOption:Column = null, lineSize:Column = null,
									  startAngle:Number = App.START_ANGLE_NINTEY):void {
			removeOperators();
			var circleTreeLayout:CircleTreeLayout = new CircleTreeLayout();
			circleTreeLayout.startAngle = (startAngle) ? startAngle : App.START_ANGLE_NINTEY;
			this.vis.operators.add(circleTreeLayout);
			
			this.vis.operators.add(new BundledEdgeRouter(0.9));			
		
			this.vis.operators.add(new RadialLabeler(
				
				function(d:DataSprite):String {
					var txt:String = d.data.label;
					
					return App.addEllipsis(txt,App.MAX_LABEL_LENGTH);
				}, true,fmt, eq("childDegree",0))); // leaf nodes only
			this.vis.operators.last.textMode = TextSprite.EMBED; // embed fonts!	
			if(lineOption) {
				this.vis.operators.add(createColorOperator(lineOption));
			}
			/*this.vis.operators.add(new PropertyEncoder(
				{alpha: div(1,"points.length")}, Data.EDGES));*/
			unhighlight();
			this.vis.update();
		}
		
		private function removeOperators():void{
			// remove operators if present...
			if(this.vis.operators.length > 0){
				this.vis.operators.clear();
			}
			
		}
		
		private function removeControls() :void {
			
			//remove controls if present...
			if (this.vis.controls.length > 0){
				this.toolTip.removeEventListener(TooltipEvent.SHOW, showToolTip);
				this.edgeTool.removeEventListener(TooltipEvent.SHOW, showToolTipForEdge);
				this.vis.controls.clear();
			};
		}
		
		private function addControls() : void {
			removeControls();
			this.vis.controls.add((toolTip = new TooltipControl(NodeSprite, null, showToolTip)));
			toolTip.showDelay = 100;
			this.vis.controls.add((edgeTool = new TooltipControl(EdgeSprite, null, showToolTipForEdge)));
			edgeTool.showDelay = 100;
			
			this.vis.controls.add(new ClickControl(NodeSprite, 1, highlight, unhighlight));
			this.vis.update();
		}
		
		public function highlightSelectedEdges(currentColumn:Column,edgeText:String):void {
			
			for each (var edge:EdgeSprite in this.vis.data.edges){
				
				edge.alpha = 0.2;
				edge.lineWidth = 1;
				if(edgeText == App.addEllipsis(edge.data[currentColumn.label],App.MAX_LEGEND_LENGTH)) {
					edge.alpha = 1;
					edge.lineWidth = 2;
				}
				
			}
			
		}
		public function bringVisToNormal():void {
			this.vis.data.edges.setProperties({
				alpha:1,
				lineWidth:1
			});	
		}
		private function highlight(event:SelectionEvent) : void {
			
			// make edges semi-transparent for all the edges, set edges connected to selected node to be fully opaque
			this.vis.data.edges.setProperties({
				alpha:0.2
			});		
				
			event.node.visitEdges(function (e:EdgeSprite):void{
				e.alpha = 1;	
				e.lineWidth = 2;
				
			}, NodeSprite.ALL_LINKS);
		}
		
		private function unhighlight (n : * = null):void {			
			// make edges semi-transparent for all the edges, set edges connected to selected node to be fully opaque
			this.vis.data.edges.setProperties({
				alpha: 1,
				lineWidth:1
			});			 
		}
		private function toolTipCss(tooltip:DisplayObject):void {
			TextSprite(tooltip).textField.borderColor = 0xffe0dddd;
			TextSprite(tooltip).textField.backgroundColor = 0xffffffff;			
			TextSprite(tooltip).textFormat = new TextFormat("Verdana", 10);
			
			
		}
		
		/**
		 * Method to show tooltip for nodes
		 * @param event
		 * 
		 */		
		private function showToolTip(event:TooltipEvent):void{
			toolTipCss(event.tooltip);			
			TextSprite(event.tooltip).htmlText = "<b>" + event.node.data.label + "</b><br>";	
			
			
		}
		
		private function showToolTipForEdge(event : TooltipEvent):void {
			toolTipCss(event.tooltip);	
			var edgeData:Object =  event.edge.data;
			var toolTipText : String = "";
			for ( var key:Object in edgeData) {
				if(this.timeLine){
					if(key == this.timeLine.dateField){
						toolTipText += key + " : " + DateUtil.convertToString(edgeData[key]) + "<br>";
					} else {
						toolTipText += key + " : " + edgeData[key] + "<br>";
					}
				} else {
					toolTipText += key + " : " + edgeData[key] + "<br>";
				}
				
			}
			
			TextSprite(event.tooltip).htmlText = toolTipText;
		}
		
		private function createLegendElements(edge:EdgeSprite):void {
			if(!_legendElements) {
				_legendElements = new Dictionary();
			}
			for (var col:Object in edge.data) {
				
				var map:Dictionary = new Dictionary ();
				map[edge.data[col]] = edge;
				
				
				if(_legendElements[col]){
					if(!_legendElements[col][edge.data[col]]){
						_legendElements[col][edge.data[col]]=edge;
					}
				} else {
					_legendElements[col] = map;
				}				
			}
		}		
		
		private function fillColumnLabelsFromRow(row:Object):void {
			for(var i:uint = 0; i < row.c.length; i++){
				this.columns[i].label = row.c[i].v;
			}
		}
		private function getRowData(row:Object):Object{
			
			var map:Dictionary = new Dictionary();
			for(var i:uint = 0; i < this.columns.length; i++){
				
				if(this.columns[i].type == Column.DATE) {
					
					
					if(!this.timeLine){
						this.timeLine = new TimeLine();
						this.timeLine.dateField = this.columns[i].label;
					}
					
					map[this.timeLine.dateField]= ((row) ? 
						((row.c[i]) ? 
							((row.c[i].v) ? Dates.addMonths(DateUtil.convertToDate(row.c[i].v),1) : "") 
							: "") 
						: "");	
					
					if(this.timeLine.maxDate == null){
						this.timeLine.maxDate = map[this.timeLine.dateField];
						this.timeLine.minDate = map[this.timeLine.dateField];
					} else {
						if(DateUtil.lte(this.timeLine.maxDate, map[this.timeLine.dateField])){
							this.timeLine.maxDate = map[this.timeLine.dateField];
						}
						if(DateUtil.lte(map[this.timeLine.dateField], this.timeLine.minDate )){
							this.timeLine.minDate = map[this.timeLine.dateField];
						}
					}
				} else {
					var columnName:String = ((this.columns[i].label)?this.columns[i].label:this.columns[i].id);
					map[columnName]= ((row) ?
						((row.c[i]) ? 
							((row.c[i].v) ? row.c[i].v : "") 
							: "") 
						: "");
				}
			}
			
			return map;
		}				
		
		private function buildData():Data{
			if(this.jsonString==null){
				return null;
			} else {
				var data:Data = new Data();	
				var tree:Tree = new Tree();
				
				var map:Dictionary = new Dictionary();
				var jsonObj:Object = JSON.decode(this.jsonString);
				this.columns = jsonObj.cols;
				var rows:Array = jsonObj.rows;	
				if(this.isLableNull) {
					if(rows && rows.length > 0){
						fillColumnLabelsFromRow(rows[0]);
					}
				}
						
				tree.root = data.addNode({"label":"root"});
				for(var i:uint = (this.isLableNull ? 1 : 0); i < rows.length; i++){
					if(!rows[i].c || !rows[i].c[0] || !rows[i].c[1]){
					 break;
					} 
					if(rows[i].c[0].v == "") break;					
					var col1:String=rows[i].c[0].v;					
					var col2:String=rows[i].c[1].v;				
					if(!map[col1]){
						map[col1]=data.addNode({"label":col1});						 
						tree.addChild(tree.root,map[col1]);
					}
					if(!map[col2]){
						map[col2]=data.addNode({"label":col2});						
						tree.addChild(tree.root,map[col2]);
					}					
							
					createLegendElements(data.addEdgeFor(map[col1],map[col2],false,getRowData(rows[i])));	
					
				}	
				for each (var u:NodeSprite in tree.nodes) {
					u.sortEdgesBy(NodeSprite.CHILD_LINKS, "target.data.label");
				}
				data.tree = tree;
				
				return data;
			}
		}
		
		/**
		 * 
		 * @param bounds
		 * 
		 */		
		override public function resize(bounds:Rectangle):void{
			var d:Number;
			
			if (vis){
				d = Math.min(bounds.width, bounds.height); // automatically size labels based on bounds
				//vis.data.nodes.setProperty("props.label.size",(d <= 650 ? 9 : d <= 725 ? 10 : 11));
				
				vis.bounds.x = bounds.x+20;	// compute the visualization bounds
				vis.bounds.y = (bounds.y + (0.06 * bounds.height));
				vis.bounds.width = bounds.width;
				vis.bounds.height = (bounds.height - (0.1 * bounds.height));
				
				
				vis.update();// update
				
				// forcibly render to eliminate partial update bug, as
				// the standard RENDER event routing can get delayed.
				// remove this line for faster but unsynchronized resizes
				DirtySprite.renderDirty();
				
			};
		}

		public function get columns():Array
		{
			return _columns;
		}

		public function set columns(cols:Array):void
		{  _columns = new Array ();
			for each (var col:Object in cols) {		
				var column:Column = new Column ();
				column.fill(col);
				if(column.label == ""){
					this.isLableNull = true;
				}
				_columns.push(column);
			}
		}

		public function get legendElements():Dictionary
		{
			return _legendElements;
		}

		public function set legendElements(value:Dictionary):void
		{
			_legendElements = value;
		}

		public function get currentLineOption():Column
		{
			return _currentLineOption;
		}

		public function set currentLineOption(value:Column):void
		{
			_currentLineOption = value;
		}
		
		public function get timeLine():TimeLine
		{
			return _timeLine;
		}

		public function set timeLine(value:TimeLine):void
		{
			_timeLine = value;
		}


	}
}